const { v4:uuidv4 } = require('uuid');

const getAllFields = entityProxy => {
	return entityProxy.beanDefine.fields.filter(f => f.hasAnnotation('Field'))
}

const getAllFieldName = entityProxy => {
	return ['id',...getAllFields(entityProxy).map(f => f.name),'date'];
}


const reconstructJoinOne = f => {
	const anno = f.getAnnotation("JoinOne")
	const {name} = anno;
	//value 父表
	const {table,column='id'} = anno.param;
	if(!table){
		throw `the JoinOne must take param [table=xx]`
	}
	return {name,table,column};
}

const entityProxyTableMap = {};


//创建表
const createBeanTable = entityProxy => {

	//默认参数
	const defaultParam = {notNull:false,length:255}

	//字段类型映射
	const typeMap = {
		'String':'VARCHAR',
		'Integer':'INT',
		'Float':'FLOAT',
		'Text':'TEXT',
		'Date':'DATE'
	}

	const {bean,beanDefine} = entityProxy;

	const tableName = entityProxy.getTableName();

	entityProxyTableMap[tableName] = entityProxy;

	//获取需要建表的字段
	const filedStrs = getAllFields(entityProxy)
		.map(f => {

			const annotation = f.getAnnotation('Field')

			const {name} = annotation;

			const param = {...defaultParam,...annotation.param}

			const {type,notNull=false,length,unique=false} = param;

			const mapType = typeMap[type]

			if(!mapType){
				throw `Field Annotation type:${type} error!`
			}

			if(mapType === 'VARCHAR'){
				return `${f.name} ${mapType}(${length}) ${notNull ? 'NOT NULL' :''} ${unique ? 'unique':''}`;
			}

			return `${f.name} ${mapType} ${notNull ? 'NOT NULL' :''}`;

		}).join(',\n')

	//获取外键
	const fkStrs = getAllFields(entityProxy).filter(f => f.hasAnnotation('JoinOne')).map(f => {

		const {name,table,column} = reconstructJoinOne(f);

		return `CONSTRAINT fk_${table} FOREIGN KEY(${f.name}) REFERENCES ${table}(${column}) \n`;
	}).join(',\n')
	

	const createTableSqlStr = `CREATE TABLE IF NOT EXISTS ${tableName}( \nid TEXT PRIMARY KEY NOT NULL, \n`+ 
							filedStrs + 
							`,\ndate TIMESTAMP default (datetime('now','localtime')) \n`+
							(fkStrs ? ','+fkStrs:'')+`);`;

	return createTableSqlStr;
}



const analySaveData = (entityProxy,saveData)=>{

	const insertData = {id:uuidv4(),...saveData};

	const allFields = getAllFieldName(entityProxy);

	const fields = allFields.slice(0,allFields.length-1);//去掉末尾的date 因为是自动插入

	const sql = `INSERT INTO ${entityProxy.getTableName()}(${fields.join(',')}) VALUES(${ fields.map(v => '?').join(',') });`;

	const param = fields.map(f => typeof insertData[f] !== undefined ? insertData[f] : null)

	return {fields,sql,param,id:insertData.id};
}


const analyGetDataById = (entityProxy,id)=>{

	const sql = `SELECT ${ getAllFieldName(entityProxy).join(",") } FROM ${entityProxy.getTableName()} WHERE id=?;`;

	return {sql,param:[id]}
}

const analyUpateData = (entityProxy,tragetBean)=>{

	const allFields = getAllFieldName(entityProxy);

	const updateFields = allFields.slice(1,allFields.length);

	const param = updateFields.map(f => tragetBean[f]);

	const sql = `UPDATE ${entityProxy.getTableName()} SET ${ updateFields.map(f => `${f}=?`).join(',') } WHERE id='${tragetBean.id}'`;

	return {param,sql};
}



const copyRowToBean =  (entityProxy,data)=>{

		const {bean,beanDefine} = entityProxy;

		const tBean = new bean.constructor();

		for(let p in data){
			tBean[p] = data[p];
		}


		//更新函数 返回Promise
		tBean.$update = async ()=> await entityProxy.update(tBean);
		tBean.$remove = async ()=> await entityProxy.remove(tBean.id);


		const proxyMethods = [];

		entityProxy.beanDefine.fields.filter(f => f.hasAnnotation('JoinOne')).forEach(f => {

			const {name,table,column} = reconstructJoinOne(f);

			const tname = f.name.substring(0,f.name.length-2);

			const extFieldName = `$${tname}`;

			tBean[extFieldName] = null;

			const getMethod = `$get${ tname[0].toUpperCase()}${tname.substring(1)}`

			proxyMethods.push(getMethod);

			// void => Promise
			tBean[getMethod] = async (firstCache={})=>{

				//缓存
				if(tBean[extFieldName])
					return tBean[extFieldName];

				//目标对象表
				const targetEntityProxy = entityProxyTableMap[table];

				if(!targetEntityProxy)
					throw `table:${table} not exist!`

				//目标id
				const targetId = tBean[f.name];
				

				/**
				 * 目前先不考虑了 毕竟也不是为了做高性能应用！
				 * 二级缓存  
				 * 先判断是否有二级缓存，如果有从二级缓存取。
				 * 						没有
				 * 							一级缓存是否存在？ 从一级中取出：从数据库中取出，赋值给一级缓存
				 * 					  赋值给属性
				 * 
				 * */

				//从一级缓存中取出  或者  从数据库中取出
				tBean[extFieldName] = firstCache[targetId] ? firstCache[targetId] : await targetEntityProxy.getById(targetId);

				//设置缓存
				firstCache[targetId] = tBean[extFieldName];

				return tBean[extFieldName];
			}

		})

		return {tBean,proxyMethods};
}

const rowConvertToBean = async (entityProxy,rows,isUrgent) => {

	const outs = [];

	//一级缓存
	const firstCache = {};

	for(let i=0;i<rows.length;i++){

		const {tBean,proxyMethods} = copyRowToBean(entityProxy,rows[i]);

		if(isUrgent && proxyMethods.length > 0){

			for(let i=0;i<proxyMethods.length;i++){
				await tBean[proxyMethods[i]](firstCache);
			}

		}

		outs.push(tBean)

	}

	return outs;
}


const convertLeftJoinCondition = (tableAlias,entityProxy,ks,iSupply,out=[])=>{

	const i = iSupply();

	if(ks.length === 0)
		return {tableAlias,leftJoin:out};

	const left = ks.shift();

	const joinOneFields = getAllFields(entityProxy).filter(f => f.hasAnnotation('JoinOne'))

	const fieldJoinOneField = joinOneFields.find(jf => jf.name === left)

	if(!reconstructJoinOne){
		throw `not find field:${left} or not have Annotation @JoinOne `
	}

	const {table,column} = reconstructJoinOne(fieldJoinOneField);

	const tmp = `s${i}`

	const leftJoinStr = `LEFT JOIN ${table} AS ${tmp} ON ${tableAlias}.${left}=${tmp}.${column}`;

	out.push(leftJoinStr);

	const entityProxyTable = entityProxyTableMap[table];

	if(!entityProxyTable){
		throw `entityProxyTableMap: ${table} not exist!`
	}

	return convertLeftJoinCondition(tmp,entityProxyTable,ks,iSupply,out);

}


const convertSearch = (entityProxy,searchData)=>{

	//需要外联的字段
	const joinOneFields = getAllFields(entityProxy).filter(f => f.hasAnnotation('JoinOne'))

	//查询对象保留的查询关键字
	const extParam = ['$pageSize','$curPage','$count'];

	//查询的key
	const key = Object.keys(searchData).filter(k => extParam.indexOf(k) === -1);

	//调用参数
	const oriParam = key.map(k => searchData[k]);

	//查出来的字段
	const selectFields = getAllFieldName(entityProxy).map(f => `t.${f}`).join(",")

	//表前缀标识
	let i =0;

	//左链接的语句集合
	let leftJoinList = [];

	//左链接映射  防止重复链接
	let leftJoinMap = {};

	let keyIndex = 0;

	//预先处理级联查询的情况 分离出真实的查询字段
	const allSearchData = key.map(k => {
		

		if(k.indexOf('$') > -1){

			const kLastIndex = k.lastIndexOf('$');

			//链接语句
			const leftJoinStr = k.substring(0,kLastIndex);

			//条件查询
			const searchField = k.substring(kLastIndex+1) ;

			//如果已经链接过
			if(leftJoinMap[leftJoinStr]){

				return `${leftJoinMap[leftJoinStr]}.${searchField}`;

			}else{

				//建立链接语句

				const {tableAlias,leftJoin} = convertLeftJoinCondition('t',entityProxy,leftJoinStr.split('$'),()=>i++)

				leftJoinList = [...leftJoinList,...leftJoin]

				//添加缓存
				leftJoinMap[leftJoinStr] = tableAlias;

				return `${tableAlias}.${searchField}`;
			}

		}else{
			return `t.${k}`;
		}

	}).map(k => {
		//处理字段查询操作

		const currentParam = oriParam[keyIndex++];

		if(k.indexOf('_') === -1){

			return [`AND ${k}=?`,currentParam];

		}else{

			const [f,condition] = k.split('_')

			switch(condition){

				//模糊查询
				case 'fuzzy':  

					return [`AND ${f} LIKE ?`,`%${currentParam}%`];

				//数据查询
				case 'in':
					return [`AND ${f} in (${convertInConditionParam(currentParam)})`];

				case 'lt':
					return [`AND ${f} < ?`,currentParam];

				case 'lte':
					return [`AND ${f} <= ?`,currentParam];

				case 'lg':
					return [`AND ${f} > ?`,currentParam];

				case 'lge':
					return [`AND ${f} >= ?`,currentParam];

				default:
					throw `can not operate the condition:${condition}`;

			}

		}

	});

	//取出查询search
	const sqlSearch = allSearchData.map(v => v[0]).join(' ')

	//取出实际参数
	const param = allSearchData.filter(v => v.length == 2).map(v => v[1])

	//SELECT ${selectFields} \n
	const sql = `FROM ${entityProxy.getTableName()} AS t \n${leftJoinList.join('\n')}  \nWHERE 1=1 ${sqlSearch} `;

	return {sql,selectFields,param};
}


const convertInConditionParam = datas => {
	return '('+datas.map(d => {
			if(typeof d === 'string')
				return `'${d}'`;
			return d;
		}).join(',')+')';
}

const groupArray = (data, cols) => {
    const r = data.reduce((r, t) => {
        r.current.push(t);
        if (r.current.length === cols) {
            r.list.push(r.current);
            r.current = [];
        }
        return r;
    }, { list: [], current: [] });

    if (r.current.length) {
        r.list.push(r.current);
    }

    return r.list;
}



module.exports = {
	createBeanTable,
	analySaveData,
	analyGetDataById,
	getAllFieldName,
	analyUpateData,
	copyRowToBean,
	rowConvertToBean,
	convertSearch,
	convertInConditionParam,
	groupArray
	}
